// message parser

#include "parser.h"

#include <array>
#include <algorithm>
#include "cpp14.h"

#include "rlutil.h"

namespace Parser {

int tokenColor(TokenType type)
{
    switch (type) {
        case TokenType::open:
            return 0;
        case TokenType::close:
            return 0;
        case TokenType::identifier:
            return 1;
        case TokenType::variable:
            return 2;
        case TokenType::number:
            return 3;
        case TokenType::eol:
            return 0;
        case TokenType::comment:
            return 7;
        case TokenType::keyword:
            return 4;
        case TokenType::op:
            return 5;
        case TokenType::assignment:
            return 6;
    }
}

static bool isKeyword(const std::string &str)
{
    static const std::array<const std::string, 10> keywords{{ "role", "input", "base",  "init", "true",
                                                       "does", "next",   "legal", "goal", "terminal" }};

    return std::find(keywords.begin(), keywords.end(), str) != keywords.end();
}

static bool isOperator(const std::string &str)
{
    static const std::array<const std::string, 3> ops{ { "and", "not", "or" } };

    return std::find(ops.begin(), ops.end(), str) != ops.end();
}

/*
static bool isAssignment(const std::string &str)
{
    return !str.compare("<=");
}
*/

static std::string getString(std::istream &istr)
{
    std::string str;
    while (!istr.eof()) {
        char c;
        istr.get(c);
        // note that the matchname sometimes contains periods
        if (isalnum(c) || (c == '.') || (c == '_') || (c == '?')) {
            str += tolower(c); // case-insensitive
            continue;
        }
        istr.putback(c);
        break;
    }

    return str;
}

static std::string getComment(std::istream &istr)
{
    std::string str;
    while (!istr.eof()) {
        char c;
        istr.get(c);
        // note that the matchname sometimes contains periods
        if (c != '\n') {
            str += c;
            continue;
        }
        istr.putback(c);
        break;
    }

    return str;
}

std::vector<Token> lexStream(std::istream &istr)
{
    std::vector<Token> result;

    while (!istr.eof()) {
        char c = '#';
        istr.get(c);
        if (c == '(') {
            result.push_back({ std::string{ c }, TokenType::open });
        } else if (c == ')') {
            result.push_back({ std::string{ c }, TokenType::close });
        } else if (c == '\n') { // only check linefeed character for end-of-line
            result.push_back({ std::string{ c }, TokenType::eol });
        } else if (c == '<') { // assume <= operator
            istr.get(); // advance
            result.push_back({ std::string{ "<=" }, TokenType::assignment });
        } else if (c == ';') {
            istr.putback(c);
            result.push_back({ getComment(istr), TokenType::comment });
        } else if (c == '?') {
            istr.putback(c);
            result.push_back({ getString(istr), TokenType::variable });
        } else if (isalpha(c)) {
            istr.putback(c);
            std::string word = getString(istr);
            if (isKeyword(word)) {
                result.push_back({ word, TokenType::keyword });
            } else if (isOperator(word)) {
                result.push_back({ word, TokenType::op });
            } else {
                result.push_back({ word, TokenType::identifier });
            }
        } else if (isdigit(c)) {
            istr.putback(c);
            result.push_back({ getString(istr), TokenType::number });
        }            
    }

    return result;
}

void printTokens(std::vector<Token> const &tokens, std::ostream &ostr)
{
    printTokens(tokens.begin(), tokens.end(), ostr);
}

void printTokens(TokenConstIt first, TokenConstIt last, std::ostream &ostr)
{
    for ( ; first != last; ++first) {
        auto tok = *first;
        rlutil::setColor(Parser::tokenColor(tok.type));
        ostr << tok.str << " ";
    }
    rlutil::setColor(0);
    ostr << std::endl;
}


static TokenConstIt nextNonTrivial(TokenConstIt it)
{
    ++it;
    while ((it->type == TokenType::eol) || (it->type == TokenType::comment)) {
        ++it;
    }
    return it;
}

static TokenConstIt nextClose(TokenConstIt it)
{
    ++it;
    while (it->type != TokenType::close) {
        ++it;
    }
    return it;
}


// don't include last token
GdlProposition::GdlProposition(TokenConstIt first, TokenConstIt last)
{
    strs.emplace_back(first->str);
    for (auto t = first+1; t != last; t = nextNonTrivial(t)) {
        strs.emplace_back( t->str );
    }
}


static std::unique_ptr<GdlTerm> parseTerm(TokenConstIt &first, TokenConstIt last);

static bool keywordHasExtra(std::string kwd) {
    bool ret = false;
    if (!kwd.compare("does")) {
        ret = true;
    } else if (!kwd.compare("legal")) {
        ret = true;
    } else if (!kwd.compare("goal")) {
        ret = true;
    } else if (!kwd.compare("input")) {
        ret = true;
    } else if (!kwd.compare("role")) {
        ret = true;
    }
    return ret;
}
// precondition :  first points to keyword token
// postcondition: first points to last token of keyword thing
static std::unique_ptr<GdlKeywordProposition> parseKeyword(std::vector<Parser::Token>::const_iterator &first,
                                                           std::vector<Parser::Token>::const_iterator last)
{
    std::unique_ptr<GdlKeywordProposition> kwdprop{ new GdlKeywordProposition() };
    kwdprop->keyword = first->str;
    if (first->str != "terminal") { // skip all if "terminal"
        if (keywordHasExtra(first->str)) {
            first = nextNonTrivial(first);
            kwdprop->extra = first->str;
        }        
    
        if (kwdprop->keyword != "role") {  // role has only an extra an no arguments
            first = nextNonTrivial(first);
            auto term = parseTerm(first, last);

            kwdprop->term = *dynamic_cast<GdlProposition *>(term.get()); // woah ugly!!!
        }
    }


    return std::move(kwdprop);
}

// runs recursively through the tokenlist, writes to first!
// precondition: first initially points to the one-word proposition or opening paren (
// postcondition: first points to closing paren ) or to last token of the term
static std::unique_ptr<GdlTerm> parseTerm(TokenConstIt &first, TokenConstIt last)
{
    if ((first->type == TokenType::identifier) || (first->type == TokenType::variable) ||
        (first->type == TokenType::number)) {
        return std::make_unique<GdlProposition>(first->str);
    }

    if (first->type == TokenType::keyword) {  // terminal keyword does not need parens around it
        auto kwdterm = parseKeyword(first, last);
        return std::move(kwdterm);
    }

    if (first->type != TokenType::open) {
        std::cout << "unexpected token = " << first->str << " " << (int)first->type << std::endl;
        throw;
    }
    first = nextNonTrivial(first);

    if ((first->type == TokenType::identifier) || (first->type == TokenType::variable)) {
        auto temp = first;
        first = nextClose(first);
        return std::make_unique<GdlProposition>(temp, first); 
    } else if (first->type == TokenType::keyword) {
        auto kwdterm = parseKeyword(first, last);
        first = nextNonTrivial(first);
        return std::move(kwdterm);
    } else if (first->type == TokenType::op) {
        if (!first->str.compare("not")) {
            auto notterm = std::make_unique<GdlNot>();
            first = nextNonTrivial(first);
            notterm->term = parseTerm(first, last); // recurse
            first = nextNonTrivial(first);
            return std::move(notterm);
        } else if (!first->str.compare("or")) {
            auto orterm = std::make_unique<GdlOr>();
            first = nextNonTrivial(first); // the opening brace
            while ((first != last) && (first->type != TokenType::close)) {
                orterm->terms.emplace_back( parseTerm(first, last) );
                first = nextNonTrivial(first);
            }
            return std::move(orterm);
        } else if (!first->str.compare("and")) {
            auto andterm = std::make_unique<GdlAnd>();
            first = nextNonTrivial(first); // the opening brace
            while ((first != last) && (first->type != TokenType::close)) {
                andterm->terms.emplace_back( parseTerm(first, last) );
                first = nextNonTrivial(first);
            }
            return std::move(andterm);
        }
    } else if (first->type == TokenType::open) {
        first = nextNonTrivial(first);
        auto term = parseTerm(first, last);
        first = nextNonTrivial(first);
        return std::move(term);
    }

    std::cout << "should not be here... token = " << first->str << " " << (int)first->type << std::endl;
    throw;  // should be here!
}

// precondition: first points to assignment token and last points to closing paren
static std::unique_ptr<GdlAssignment> parseAssignment(std::vector<Parser::Token>::const_iterator first,
                                                      std::vector<Parser::Token>::const_iterator last)
{
    std::unique_ptr<GdlAssignment> ass{ new GdlAssignment() };
    first = nextNonTrivial(first); // skip assignment token

    while (first != last) {
        ass->terms.emplace_back( parseTerm(first, last) );
        first = nextNonTrivial(first);
    }

    return std::move(ass);
}



static GdlSentence parseSentence(std::vector<Parser::Token>::const_iterator first,
                                 std::vector<Parser::Token>::const_iterator last)
{
    // bugs on empty sentence

    GdlSentence ret;   

    printTokens(first, last, std::cout);

    for (; first != last; ++first) {
        if (first->type == TokenType::assignment) {
            ret.term = parseAssignment(first, last);
            break;
        } else if (first->type == TokenType::keyword) {
            ret.term = parseKeyword(first, last);
            break;
        } else if (first->type == TokenType::identifier) {
            // it must be a simple proposition
            ret.term = std::make_unique<GdlProposition>(first, last);
            break;
        }
    }

    //std::cout << ret.toString() << std::endl;

    return ret;
}

std::vector<GdlSentence> parseTokens(std::vector<Parser::Token>::const_iterator first,
                                     std::vector<Parser::Token>::const_iterator last)
{
    std::vector<GdlSentence> sentences;

    // process the token list
    auto open_tok = first; // the token that starts a gdl sentence
    int num_open = 0;
    for (auto tok = first; tok != last; ++tok) {
        // skip most tokens, only handle open and close tokens to form sentences
        // if matching open and close braces found, call parseSentence to deal with the found sentence
        switch (tok->type) {
            case TokenType::open:
                num_open++;
                if (num_open == 1)
                    open_tok = tok; // save the first open token
                break;
            case TokenType::close:
                num_open--;
                if (num_open == 0) {
                    sentences.emplace_back(parseSentence(nextNonTrivial(open_tok) , tok));
                }
                break;
            default:
                // skip all other tokens
                break;
        }
    }

    return sentences;
}


} // namespace Parser
